package optimizers.ganeat;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;

import com.itextpdf.text.pdf.PdfStructTreeController.returnType;
import common.Static;

import optimizers.commons.RunConfig;
import model.Enzyme;
import model.OligoSystemGeneral;
import util.MyUtil;
import visualization.PlotExpData;
import fitness.FitnessResult;

public class Individual implements Cloneable, Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	public ArrayList<Node> nodeGenes = new ArrayList<Node>();
	public ArrayList<Connection> connectionGenes = new ArrayList<Connection>();
	public Map<String, Integer> innovations;
	public Map<String, String> nodeNames;
	public FitnessResult fitnessResult;
	public Individual parent1;
	public Individual parent2;
	public int generationIndex;
	public int individualIndex;
	public String mutationInfo = "Not mutated";
	public String modelString = "";
	public Individual spicies;
	public Individual indivCompared;
	public double exo;
	public double pol;
	public double nick;
	public RunConfig config;

	protected Random rand = new Random();
	protected boolean minimize = false;

	public Individual(Map<String, Integer> innovations,
			Map<String, String> nodeNames, int geneIndex, int indivIndex,
			RunConfig config) {
		this.innovations = innovations;
		this.nodeNames = nodeNames;
		this.generationIndex = geneIndex;
		this.individualIndex = indivIndex;
		this.config = config;
	}

	public Individual(Map<String, Integer> innovations,
			Map<String, String> nodeNames, int geneIndex, int indivIndex,
			RunConfig config, String text) {
		this.innovations = innovations;
		this.nodeNames = nodeNames;
		this.generationIndex = geneIndex;
		this.individualIndex = indivIndex;
		this.config = config;
		text = text.replace("\r\n", "\n");
		String[] lines = text.split("\n");
		int mode = 0;
		for (int i = 0; i < lines.length; i++) {
			if (lines[i].contains("Node gens:")) {
				mode = 1;
				continue;
			} else if (lines[i].contains("Connection gens:")) {
				mode = 2;
				continue;
			} else if (lines[i].contains("Exo:")) {
				mode = 3;
			} else if (lines[i].contains("Pol:")) {
				mode = 4;
			} else if (lines[i].contains("Nick:")) {
				mode = 5;
			}
			switch (mode) {
			case 1:
				String[] tmp1 = lines[i].trim().split(" ");
				nodeGenes.add(new Node(tmp1[0], Double.valueOf(tmp1[1]), Double
						.valueOf(tmp1[2]),
						tmp1[0].startsWith("I") ? Node.INHIBITING_SEQUENCE
								: Node.SIMPLE_SEQUENCE,
						tmp1.length >= 4 ? tmp1[3] : null));
				break;
			case 2:
				String[] tmp2 = lines[i].trim().split(" ");
				String[] nodes = tmp2[1].split("->");
				addConnection(getNode(nodes[0]), getNode(nodes[1]),
						tmp2[2].equals("true"), Double.valueOf(tmp2[3]));
				break;
			case 3:
				String[] tmp3 = lines[i].trim().split(" ");
				exo = Double.valueOf(tmp3[1]);
				break;
			case 4:
				String[] tmp4 = lines[i].trim().split(" ");
				pol = Double.valueOf(tmp4[1]);
				break;
			case 5:
				String[] tmp5 = lines[i].trim().split(" ");
				nick = Double.valueOf(tmp5[1]);
				break;
			}
		}
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("Node gens:\n");
		for (Node node : nodeGenes) {
			double parameter = node.parameter;
			if (node.type == Node.INHIBITING_SEQUENCE) {
				for (Node tmp1 : this.nodeGenes) {
					for (Node tmp2 : this.nodeGenes) {
						if (node.name.equals("I" + tmp1.name + tmp2.name)) {
							switch (config.inhK.optimizeMechanism) {
							case RunConfig.INHK_OPTIMIZE_NO_PRESET:
								parameter = MyUtil.calculateInhK(tmp1.string,
										tmp2.string);
								break;
							case RunConfig.INHK_OPTIMIZE_NO_7_6_RULE:
								parameter = MyUtil.calculateInhK(
										tmp1.parameter, tmp2.parameter);
								break;
							default:
								parameter = node.parameter;
								break;
							}
						}
					}
				}
			}
			builder.append(" " + node.name + " "
					+ common.Static.df4.format(parameter) + " "
					+ common.Static.df4.format(node.concentration) + " "
					+ node.string + "\n");
		}
		builder.append("Connection gens:\n");
		for (Connection connection : connectionGenes) {
			builder.append(" " + connection.innovation + " "
					+ connection.from.name + "->" + connection.to.name + " "
					+ connection.enabled + " "
					+ common.Static.df4.format(connection.parameter) + "\n");
		}
		builder.append("Exo: " + common.Static.df4.format(exo) + "\n");
		builder.append("Pol: " + common.Static.df4.format(pol) + "\n");
		builder.append("Nick: " + common.Static.df4.format(nick) + "\n");
		return builder.toString();
	}

	public Connection getInhibitedConnection(Node node) {
		if (node.type == Node.INHIBITING_SEQUENCE) {
			for (Connection conn : connectionGenes) {
				if (node.name.equals("I" + conn.from.name + conn.to.name)) {
					return conn;
				}
			}
		} else {
			return null;
		}
		return null;
	}

	public Connection getConnection(int innovation) {
		for (Connection connection : connectionGenes) {
			if (connection.innovation == innovation)
				return connection;
		}
		return null;
	}

	public Node getNode(String nodeName) {
		for (Node node : nodeGenes) {
			if (node.name.equals(nodeName))
				return node;
		}
		return null;
	}

	public int getNEnabledConnections() {
		int result = 0;
		for (Connection conn : connectionGenes) {
			if (conn.enabled) {
				result++;
			}
		}
		return result;
	}

	protected double mutateParam(double oldParam, double min, double max) {
		double mutatedParam;
		if (rand.nextDouble() < Constants.probRandomParamMutate) {
			mutatedParam = MyUtil.randomDoubleLogScale(rand, min, max);
		} else {
			mutatedParam = MyUtil.gaussianTransform(rand, oldParam, min, max);
		}
		return mutatedParam;
	}

	public String mutateParameter() {
		StringBuilder info = new StringBuilder();
		info.append("Mutate Parameters:\n Nodes: \n");
		if (config.seqC.optimizeMechanism == RunConfig.SEQC_OPTIMIZE_YES) {
			info.append(" Concentrations:\n");
			for (Node node : nodeGenes) {
				if (rand.nextDouble() < Constants.probGeneMutation) {
					double oldParameter = node.concentration;
					node.concentration = mutateParam(node.concentration,
							config.seqC.minValue, config.seqC.maxValue);
					info.append(node.name + ": " + oldParameter + "->"
							+ node.concentration + "\n");
				}
			}
		}
		if (config.seqK.optimizeMechanism == RunConfig.SEQK_OPTIMIZE_YES) {
			info.append("\n K params:\n");
			for (Node node : nodeGenes) {
				if (node.type == Node.SIMPLE_SEQUENCE) {
					if (rand.nextDouble() < Constants.probGeneMutation) {
						if (Constants.optimizeSeqString) {
							double oldParameter = node.parameter;
							String oldString = node.string;
							int randomUnused = getRandomUnusedStr();
							if (randomUnused != -1) {
								node.parameter = Static.seqK[randomUnused];
								node.string = Static.seqStr[randomUnused];
								info.append(node.name + ": " + oldString + "->"
										+ node.string + ";" + oldParameter
										+ "->" + node.parameter + "\n");
							} else {
								info.append("Cannot mutate seqK, max number of sequences\n");
							}
						} else {
							double oldParameter = node.parameter;
							node.parameter = mutateParam(node.parameter,
									config.seqK.minValue, config.seqK.maxValue);
							info.append(node.name + ": " + oldParameter + "->"
									+ node.parameter + "\n");
						}
					}
				}
			}
		}

		if (config.template.optimizeMechanism == RunConfig.TEMPLATE_OPTIMIZE_YES) {
			info.append("\n Connections: \n");
			for (int i = connectionGenes.size() - 1; i >= 0; i--) {
				Connection connection = connectionGenes.get(i);
				boolean subZero = false;
				if (connection.enabled
						&& rand.nextDouble() < Constants.probGeneMutation) {
					double oldParameter = connection.parameter;
					boolean disabled = false;
					double newParameter = mutateParam(connection.parameter,
							config.template.minValue, config.template.maxValue);
					if ((rand.nextDouble() < MyUtil
							.probDisableConnection(connection.parameter) || Double
							.isNaN(newParameter))
							&& getNEnabledConnections() > 1) {
						connection.enabled = false;
						disabled = true;
					} else {
						if (Double.isNaN(newParameter)) {
							connection.parameter = 0.001;
						} else {
							connection.parameter = newParameter;
						}
					}
					if (connection.parameter < 0) {
						subZero = true;
						connection.enabled = false;
						connection.parameter = -connection.parameter;
						if (connection.to.type == Node.INHIBITING_SEQUENCE) {
							Connection inhibitedConnection = getInhibitedConnection(connection.to);
							if (inhibitedConnection != null) {
								addConnection(connection.from,
										inhibitedConnection.to, true,
										connection.parameter);
							}
						} else {
							mutateAddInhibition(connection.from, connection.to,
									config.template.defaultValue);
						}
					}
					if (disabled) {
						info.append(connection.from.name + connection.to.name
								+ ": Disabled\n");
					} else {
						info.append(connection.from.name + connection.to.name
								+ ": " + oldParameter + "->"
								+ (subZero ? "(-)" : "") + connection.parameter
								+ "\n");
					}
				}
			}
		}
		if (config.polActivity.optimizeMechanism == RunConfig.POL_OPTIMIZE_YES) {
			double oldParameter = pol;
			pol = MyUtil.gaussianTransformEnzymes(rand, oldParameter,
					config.polActivity.minValue, config.polActivity.maxValue);
			info.append("\nK pol:\n" + oldParameter + "->" + pol + "\n");
		}
		if (config.exoActivity.optimizeMechanism == RunConfig.EXO_OPTIMIZE_YES) {
			double oldParameter = exo;
			exo = MyUtil.gaussianTransformEnzymes(rand, oldParameter,
					config.exoActivity.minValue, config.exoActivity.maxValue);
			info.append("\nK pol:\n" + oldParameter + "->" + exo + "\n");
		}
		if (config.nickActivity.optimizeMechanism == RunConfig.NICK_OPTIMIZE_YES) {
			double oldParameter = nick;
			nick = MyUtil.gaussianTransformEnzymes(rand, oldParameter,
					config.nickActivity.minValue, config.nickActivity.maxValue);
			info.append("\nK nick:\n" + oldParameter + "->" + nick + "\n");
		}
		mutationInfo = info.toString();
		return mutationInfo;
	}

	public Connection addConnection(Node from, Node to, boolean enabled,
			double parameter) {
		int innovation = innovations.size() + 1;
		if (innovations != null && innovations.get("a->a") == null) {
			// System.out.println("Something wrooooong");
		}
		String key = from.name + "->" + to.name;
		if (innovations.get(key) == null) {
			innovations.put(key, innovation);
		} else {
			innovation = innovations.get(key);
		}
		Connection newConnection = getConnection(innovation);
		if (newConnection == null) {
			newConnection = new Connection(innovation, from, to, enabled,
					parameter);
			connectionGenes.add(newConnection);
		} else {
			newConnection.enabled = true;
			newConnection.parameter = parameter;
		}
		return newConnection;
	}

	public Connection getConnection(String fromName, String toName) {
		for (Connection conn : connectionGenes) {
			if (conn.from.name.equals(fromName) && conn.to.name.equals(toName)) {
				return conn;
			}
		}
		return null;
	}

	public String mutateAddActivation() {
		ArrayList<Connection> possibleActivations = new ArrayList<Connection>();
		for (Node from : nodeGenes) {
			if (from.type != Node.INHIBITING_SEQUENCE) {
				for (Node to : nodeGenes) {
					if (!to.name.equals("i1") && !to.name.equals("i2")
							&& getConnection(from.name, to.name) == null) {
						possibleActivations.add(new Connection(0, from, to,
								true, 0));
					}
				}
			}
		}
		if (possibleActivations.size() == 0) {
			mutationInfo = "Cannot add activation";
		} else {
			int index = rand.nextInt(possibleActivations.size());
			Connection connection = possibleActivations.get(index);
			addConnection(connection.from, connection.to, true,
					config.template.defaultValue);
			mutationInfo = "Mutate Add Activation: " + connection.from.name
					+ "->" + connection.to.name;
		}
		return mutationInfo;

		/*
		 * Node from = null; Node to = null; boolean connectionExist = true; int
		 * tries = 0; while (connectionExist && tries < 10) { tries++; from =
		 * null; to = null; while (from == null || from.type ==
		 * Node.INHIBITING_SEQUENCE) { int index =
		 * rand.nextInt(nodeGenes.size()); from = nodeGenes.get(index); } while
		 * (to == null || to.name.equals("i1") || to.name.equals("i2")) { int
		 * index = rand.nextInt(nodeGenes.size()); to = nodeGenes.get(index); }
		 * connectionExist = false; for (Connection connection :
		 * connectionGenes) { if (connection.from.name == from.name &&
		 * connection.to.name == to.name) { connectionExist = true; break; } } }
		 * if (!connectionExist) { addConnection(from, to, true,
		 * Constants.defaultConnectionParam); mutationInfo =
		 * "Mutate Add Activation: " + from.name + "->" + to.name; } else {
		 * mutationInfo = "Full of connections"; } return mutationInfo;
		 */
	}

	public String mutateAddInhibition(Node nodeFrom, Node nodeTo,
			double parameter) {
		ArrayList<Connection> possibleConnections = new ArrayList<Connection>();
		for (Connection conn : connectionGenes) {
			if (conn.to == nodeTo && conn.enabled) {
				possibleConnections.add(conn);
			}
		}
		Connection conn = null;
		if (possibleConnections.size() == 0) {
			conn = addConnection(nodeTo, nodeTo, true,
					config.template.defaultValue);
		} else {
			conn = possibleConnections.get(rand.nextInt(possibleConnections
					.size()));
		}
		Node inhibitionNode = new Node("I" + conn.from.name + conn.to.name,
				config.inhK.defaultValue, config.seqC.defaultValue,
				Node.INHIBITING_SEQUENCE, null);
		if (getNode(inhibitionNode.name) == null) {
			nodeGenes.add(inhibitionNode);
		} else {
			inhibitionNode = getNode(inhibitionNode.name);
		}
		addConnection(nodeFrom, inhibitionNode, true, parameter);
		mutationInfo = "Mutate Add Inhibition: " + nodeFrom.name + "->"
				+ inhibitionNode.name;

		return mutationInfo;

	}

	public String mutateAddDoubleInhibition() {
		ArrayList<Connection> possibleConnections = new ArrayList<Connection>();
		for (Connection conn : connectionGenes) {
			if (conn.enabled && conn.to.type != Node.INHIBITING_SEQUENCE) {
				possibleConnections.add(conn);
			}
		}
		if (possibleConnections.size() == 0) {
			return mutateParameter();
		}
		Connection conn = possibleConnections.get(rand
				.nextInt(possibleConnections.size()));
		conn.enabled = false;
		Node nodeFrom = conn.from;
		Node nodeTo = conn.to;
		Node newNode = addNode(nodeFrom.name + "->DI->" + nodeTo.name);
		String mutate1 = mutateAddInhibition(nodeFrom, newNode,
				config.template.defaultValue);
		String mutate2 = mutateAddInhibition(newNode, nodeTo,
				config.template.defaultValue);
		mutationInfo = "Mutate Add Double Inhibition: [" + mutate1 + "] ["
				+ mutate2 + "]";
		return mutationInfo;
	}

	public String mutateAddSingleInhibition() {
		ArrayList<Node> possibleNodes = new ArrayList<Node>();
		for (Node node : nodeGenes) {
			if (node.type == Node.SIMPLE_SEQUENCE) {
				possibleNodes.add(node);
			}
		}
		if (possibleNodes.size() == 0) {
			return mutateParameter();
		}
		Node nodeFrom = possibleNodes.get(rand.nextInt(possibleNodes.size()));
		Node nodeTo = possibleNodes.get(rand.nextInt(possibleNodes.size()));
		Connection connection = getConnection(nodeFrom.name, nodeTo.name);
		if (connection != null)
			connection.enabled = false;
		return mutateAddInhibition(nodeFrom, nodeTo,
				config.template.defaultValue);
	}

	private Connection getBackConnection(Connection connection) {
		Connection result = null;
		int countIn = 0;
		int countOut = 0;
		for (Connection conn : connectionGenes) {
			if (conn.from == connection.from && conn.enabled) {
				countOut++;
			}
			if (conn != connection && conn.to == connection.from
					&& conn.enabled) {
				result = conn;
				countIn++;
			}
		}
		if (countIn == 1 & countOut == 1) {
			return result;
		} else {
			return null;
		}
	}

	protected Connection getCorrectConnection(Connection connection) {
		Connection conn = connection;
		while (getBackConnection(conn) != null
				&& getBackConnection(conn) != connection) {
			conn = getBackConnection(conn);
		}
		return conn;

	}

	public String mutateAddNode() {
		if (nodeGenes.size() == 0)
			return "Empty individual";
		if (rand.nextDouble() < 1.0f / (getNSimpleSequences() + 1)) {
			// new mutation rule
			if (rand.nextBoolean()) {
				// new node connect to a simple sequence node
				Node to = null;
				while (to == null || to.type == Node.INHIBITING_SEQUENCE) {
					int index = rand.nextInt(nodeGenes.size());
					to = nodeGenes.get(index);
				}
				String nodeOrigins = "null->" + to.name;
				Node newNode = addNode(nodeOrigins);
				addConnection(newNode, to, true, config.template.defaultValue);
				addConnection(newNode, newNode, true,
						config.template.defaultValue);
				mutationInfo = "Mutate Add Node :" + newNode.name + "->"
						+ to.name;
			} else {
				// new node connect to inhibiting sequence node
				ArrayList<Connection> possibleActivations = new ArrayList<Connection>();
				for (Connection conn : connectionGenes) {
					if (conn.to.type != Node.INHIBITING_SEQUENCE
							&& conn.enabled) {
						possibleActivations.add(conn);
					}
				}
				if (possibleActivations.size() == 0) {
					mutationInfo = "Cannot add node";
				} else {
					int index = rand.nextInt(possibleActivations.size());
					Connection connection = possibleActivations.get(index);
					String inhibitionNodeName = "I" + connection.from.name
							+ connection.to.name;
					Node inhibitionNode = getNode(inhibitionNodeName);
					if (inhibitionNode == null) {
						inhibitionNode = new Node(inhibitionNodeName,
								config.inhK.defaultValue,
								config.seqC.defaultValue,
								Node.INHIBITING_SEQUENCE, null);
						nodeGenes.add(inhibitionNode);
					}
					String nodeOrigins = "null->" + inhibitionNode.name;
					Node newNode = addNode(nodeOrigins);
					addConnection(newNode, inhibitionNode, true,
							config.template.defaultValue);
					addConnection(newNode, newNode, true,
							config.template.defaultValue);
					mutationInfo = "Mutate Add Node :" + newNode.name + "->"
							+ inhibitionNode.name;
				}
			}

		} else if (connectionGenes.size() > 0) {
			ArrayList<Connection> possibleActivations = new ArrayList<Connection>();
			for (Connection conn : connectionGenes) {
				if (conn.enabled) {
					possibleActivations.add(conn);
				}
			}
			if (possibleActivations.size() == 0) {
				mutationInfo = "Cannot Add Node";
			} else {
				Connection connection = null;

				int index = rand.nextInt(possibleActivations.size());
				connection = getCorrectConnection(possibleActivations
						.get(index));

				connection.enabled = false;
				String nodeOrigins = connection.from.name + "->"
						+ connection.to.name;
				Node newNode = addNode(nodeOrigins);
				// double firstParam = rand.nextDouble() * connection.parameter;
				// double secondParam = connection.parameter - firstParam;
				if (connection.to.type == Node.INHIBITING_SEQUENCE) {
					addConnection(connection.from, newNode, true,
							config.template.defaultValue);
				} else {
					addConnection(connection.from, newNode, true,
							connection.parameter);
				}
				addConnection(newNode, connection.to, true,
						connection.parameter);
				String oldNodeName = "I" + connection.from.name
						+ connection.to.name;
				for (Node node : nodeGenes) {
					if (node.name.equals(oldNodeName)) {
						node.name = "I" + connection.from.name + newNode.name;
						for (Connection conn : connectionGenes) {
							if (conn.to == node) {
								int innovation = innovations.size() + 1;
								String key = conn.from.name + "->"
										+ conn.to.name;
								if (innovations.get(key) == null) {
									innovations.put(key, innovation);
								} else {
									innovation = innovations.get(key);
								}
								conn.innovation = innovation;
							}
						}
						break;
					}
				}
				mutationInfo = "Mutate Add Node :" + connection.from.name
						+ "->" + newNode.name + "->" + connection.to.name;
			}
		}
		return mutationInfo;
	}

	public Node addNode(String nodeOrigins) {
		int i = nodeNames.size() + 99;
		char c = (char) i;
		String nodeName = Character.toString(c);
		if (nodeNames.get(nodeOrigins) == null) {
			nodeNames.put(nodeOrigins, nodeName);
		} else {
			nodeName = nodeNames.get(nodeOrigins);
		}
		boolean hasNodeName = true;
		i = 0;
		while (hasNodeName) {
			if (i > 0) {
				nodeName = nodeName + String.valueOf(i);
			}
			hasNodeName = false;
			for (Node node : nodeGenes) {
				if (node.name.equals(nodeName)) {
					hasNodeName = true;
					break;
				}
			}
			i++;
		}
		Node newNode = null;
		if (Constants.optimizeSeqString) {
			int randUnused = getRandomUnusedStr();
			if (randUnused != -1) {
				newNode = new Node(nodeName, Static.seqK[randUnused],
						config.seqC.defaultValue, Node.SIMPLE_SEQUENCE,
						Static.seqStr[randUnused]);
			} else {
				return null;
			}
		} else {
			newNode = new Node(nodeName, config.seqK.defaultValue,
					config.seqC.defaultValue, Node.SIMPLE_SEQUENCE, nodeName);
		}
		nodeGenes.add(newNode);
		return newNode;
	}

	public int getRandomUnusedStr() {
		int nSimpleSequences = getNSimpleSequences();
		if (nSimpleSequences >= Static.seqStr.length) {
			return -1;
		}
		ArrayList<Integer> possibleNumbers = new ArrayList<Integer>();
		for (int i = 0; i < Static.seqStr.length; i++) {
			boolean existing = false;
			for (Node node : nodeGenes) {
				if (node.type == Node.SIMPLE_SEQUENCE && node.string != null
						&& node.string.equals(Static.seqStr[i])) {
					existing = true;
					break;
				}
			}
			if (!existing) {
				possibleNumbers.add(i);
			}
		}
		return possibleNumbers.get(rand.nextInt(possibleNumbers.size()));
	}

	public void mutate() {
		double randD = rand.nextDouble();
		if (randD < Constants.probParamMutation && connectionGenes.size() > 0) {
			this.mutateParameter();
		} else {
			randD = rand.nextDouble();
			if (randD < Constants.probAddNodeMutation) {
				this.mutateAddNode();
			} else if (randD < Constants.probAddNodeMutation
					+ Constants.probAddActivation) {
				this.mutateAddActivation();
			} else if (randD < Constants.probAddNodeMutation
					+ Constants.probAddActivation
					+ Constants.probAddDoubleInhibition) {
				this.mutateAddDoubleInhibition();
			} else {
				this.mutateAddSingleInhibition();
			}
		}
	}

	public String getTextInformation() {
		String text = "";
		text += this.toString();
		if (this.fitnessResult != null) {
			text += "Fitness: " + this.fitnessResult.toString() + "\n";
		}

		text += "Mutation: " + this.mutationInfo + "\n";
		if (this.spicies != null) {
			text += "Species: " + this.spicies.generationIndex + ","
					+ this.spicies.individualIndex + "\n";
		}
		if (this.indivCompared != null) {
			text += "Indiv compared: " + this.indivCompared.generationIndex
					+ "," + this.indivCompared.individualIndex + "\n";
		}
		return text;
	}

	public JPanel printModelInTextArea() {
		JPanel info = new JPanel();
		JTabbedPane tabbedPane = new JTabbedPane();
		info.add(tabbedPane);
		JTextArea txtArea = new JTextArea();
		String text = "";
		text += this.toString();
		if (this.fitnessResult != null) {
			text += "Fitness: " + this.fitnessResult.toString() + "\n";
		}

		text += "Mutation: " + this.mutationInfo + "\n";
		if (this.spicies != null) {
			text += "Species: " + this.spicies.generationIndex + ","
					+ this.spicies.individualIndex + "\n";
		}
		if (this.indivCompared != null) {
			text += "Indiv compared: " + this.indivCompared.generationIndex
					+ "," + this.indivCompared.individualIndex + "\n";
		}
		txtArea.setText(text);
		txtArea.setWrapStyleWord(true);

		JTextPane modelString = new JTextPane();
		modelString.setText(this.modelString);
		tabbedPane.addTab("NEAT", txtArea);
		tabbedPane.addTab("NATH", modelString);
		return info;
	}

	protected Node getFirstRedundantNode() {
		for (Node node : nodeGenes) {
			boolean foundConnectionTo = false;
			for (Connection conn : connectionGenes) {
				if (conn.enabled && conn.to == node) {
					foundConnectionTo = true;
					break;
				}
			}
			if (!foundConnectionTo) {
				return node;
			}
		}
		return null;
	}

	private void removeNode(Node node) {
		for (int j = connectionGenes.size() - 1; j >= 0; j--) {
			Connection conn = connectionGenes.get(j);
			if (conn.to == node || conn.from == node) {
				connectionGenes.remove(conn);
			}
		}
		nodeGenes.remove(node);
	}

	public Individual getMinimalStructure() throws CloneNotSupportedException {
		Individual indiv = (Individual) this.clone();
		while (indiv.getFirstRedundantNode() != null
				&& indiv.getNSimpleSequences() > 1) {
			indiv.removeNode(indiv.getFirstRedundantNode());
		}
		return indiv;
	}

	public OligoSystemGeneral getOligoModel() {
		Individual miniIndiv = this;
		if (minimize) {
			try {
				miniIndiv = this.getMinimalStructure();
			} catch (CloneNotSupportedException e) {
				e.printStackTrace();
			}
		}
		Map<String, Integer> nameToInt = new HashMap<String, Integer>();
		Map<Integer, String> intToName = new HashMap<Integer, String>();
		int nSimpleSequences = 0;
		int nSequences = 0;
		for (Node node : miniIndiv.nodeGenes) {
			if (node.type == Node.SIMPLE_SEQUENCE) {
				nameToInt.put(node.name, nSimpleSequences);
				intToName.put(nSimpleSequences, node.name);
				nSimpleSequences++;
			}
		}
		nSequences = nSimpleSequences;
		for (Node node : miniIndiv.nodeGenes) {
			if (node.type == Node.INHIBITING_SEQUENCE) {
				boolean foundInhibitedActivation = false;
				for (Connection conn : this.connectionGenes) {
					if (conn.enabled
							&& node.name.equals("I" + conn.from.name
									+ conn.to.name)) {
						foundInhibitedActivation = true;
						break;
					}
				}
				if (foundInhibitedActivation) {
					nameToInt.put(node.name, nSequences);
					intToName.put(nSequences, node.name);
					nSequences++;
				}
			}
		}
		double[][][] template = new double[nSimpleSequences + 1][nSimpleSequences][nSimpleSequences];
		double[][] seqK = new double[nSimpleSequences + 1][nSimpleSequences];
		double[][] seqC = new double[nSimpleSequences + 1][nSimpleSequences];
		double[][] inhK = new double[nSimpleSequences + 1][nSimpleSequences];
		String[] seqString = new String[nSimpleSequences];

		int index;

		// simple sequences K
		for (Node node : miniIndiv.nodeGenes) {
			if (node.type == Node.SIMPLE_SEQUENCE) {
				seqK[0][nameToInt.get(node.name)] = node.parameter;
				seqString[nameToInt.get(node.name)] = node.string;
			}
		}

		// simple sequences C
		for (Node node : miniIndiv.nodeGenes) {
			if (node.type == Node.SIMPLE_SEQUENCE) {
				seqC[0][nameToInt.get(node.name)] = node.concentration;
			}
		}

		// inhibiting sequences K
		for (Node node : miniIndiv.nodeGenes) {
			if (node.type == Node.INHIBITING_SEQUENCE) {
				for (Node tmp1 : miniIndiv.nodeGenes) {
					for (Node tmp2 : miniIndiv.nodeGenes) {
						if (node.name.equals("I" + tmp1.name + tmp2.name)) {
							int index1 = nameToInt.get(tmp1.name);
							int index2 = nameToInt.get(tmp2.name);
							if (config != null) {
								switch (config.inhK.optimizeMechanism) {
								case RunConfig.INHK_OPTIMIZE_NO_PRESET:
									seqK[index1 + 1][index2] = MyUtil
											.calculateInhK(seqString[index1],
													seqString[index2]);
									break;
								case RunConfig.INHK_OPTIMIZE_NO_7_6_RULE:
									seqK[index1 + 1][index2] = MyUtil
											.calculateInhK(tmp1.parameter,
													tmp2.parameter);
									break;
								default:
									seqK[index1 + 1][index2] = node.parameter;
									break;
								}
							} else {
								seqK[index1 + 1][index2] = 0;
							}

							inhK[index1 + 1][index2] = seqK[index1 + 1][index2]
									* model.Constants.ratioInhibition;
						}
					}
				}
			}
		}

		// Activating Template Concentration
		for (Connection connection : miniIndiv.connectionGenes) {
			if (connection.from.type == Node.SIMPLE_SEQUENCE
					&& connection.to.type == Node.SIMPLE_SEQUENCE
					&& connection.enabled) {
				int index1 = nameToInt.get(connection.from.name);
				int index2 = nameToInt.get(connection.to.name);
				template[0][index1][index2] = connection.parameter;
			}
		}

		// Inhibiting Template Concentration
		for (Connection connection : miniIndiv.connectionGenes) {
			if (connection.from.type == Node.SIMPLE_SEQUENCE
					&& connection.to.type == Node.INHIBITING_SEQUENCE
					&& connection.enabled
					&& nameToInt.get(connection.to.name) != null) {
				for (Node tmp1 : this.nodeGenes) {
					for (Node tmp2 : this.nodeGenes) {
						if (connection.to.name.equals("I" + tmp1.name
								+ tmp2.name)) {
							index = nameToInt.get(connection.from.name);
							try {
								if (nameToInt.get(tmp1.name) != null) {
									int index1 = nameToInt.get(tmp1.name);
									int index2 = nameToInt.get(tmp2.name);
									template[index + 1][index1][index2] = connection.parameter;
								} else {
									System.err
											.println("Template has been pruned away");
								}
							} catch (NullPointerException e) {
								System.err
										.println("Template has been pruned away");
							}
						}
					}
				}
			}
		}

		OligoSystemGeneral oligoSystem = new OligoSystemGeneral(
				nSimpleSequences, template, seqK, inhK, seqC, new Enzyme("exo",
						exo, model.Constants.exoKmSimple,
						model.Constants.exoKmInhib), new Enzyme("nick", nick,
						model.Constants.nickKm), new Enzyme("poly", pol,
						model.Constants.polKm, model.Constants.polKmBoth),
				intToName, nameToInt, seqString);
		return oligoSystem;
	}

	public JPanel displayTimeSeriesWithButton() {
		OligoSystemGeneral oligoModel = getOligoModel();
		double[][] data = oligoModel.getTimeSeriesComplexModel();
		String[] seqNames = new String[data.length];
		for (int i = 0; i < data.length; i++) {
			seqNames[i] = oligoModel.intToName.get(i);
			if (seqNames[i] == null)
				seqNames[i] = String.valueOf(i);
		}
		final PlotExpData demo = new PlotExpData("Generation "
				+ this.generationIndex + " Index " + this.individualIndex,
				data, 0.0, 1.0, seqNames, "chart", "Time", "Expression", true,
				0, 0);
		return (demo);
	}

	public JPanel displayTimeSeries(String title) {
		OligoSystemGeneral oligoModel = getOligoModel();
		double[][] data = oligoModel.getTimeSeriesComplexModel();
		String[] seqNames = new String[data.length];
		for (int i = 0; i < data.length; i++) {
			seqNames[i] = oligoModel.intToName.get(i);
			if (seqNames[i] == null)
				seqNames[i] = String.valueOf(i);
		}
		final PlotExpData demo = new PlotExpData(title, data, seqNames, "Time",
				"Expression");
		return (demo);
	}

	public JPanel displayTimeSeries() {
		OligoSystemGeneral oligoModel = getOligoModel();
		double[][] data = oligoModel.getTimeSeriesComplexModel();
		String[] seqNames = new String[data.length];
		for (int i = 0; i < data.length; i++) {
			seqNames[i] = oligoModel.intToName.get(i);
			if (seqNames[i] == null)
				seqNames[i] = String.valueOf(i);
		}
		final PlotExpData demo = new PlotExpData("Generation "
				+ this.generationIndex + " Index " + this.individualIndex,
				data, seqNames, "Time", "Expression");
		return (demo);
	}

	public int getNSimpleSequences() {
		int nSimpleSequences = 0;
		for (Node node : this.nodeGenes) {
			if (node.type == Node.SIMPLE_SEQUENCE) {
				nSimpleSequences++;
			}
		}
		return nSimpleSequences;
	}

	public double getFitness() {
		if (this.fitnessResult != null) {
			return this.fitnessResult.finalResult();
		} else {
			return 0;
		}
	}

	public String getFitnessString() {
		if (this.fitnessResult != null) {
			return this.fitnessResult.toString();
		} else {
			return "null";
		}
	}

	public int getNumberOfTemplates() {
		int result = 0;
		for (Connection c : connectionGenes) {
			if (c.enabled)
				result++;
		}
		return result;
	}

	public double getStandardDeviationOfLogK() {
		int count = 0;
		double sum = 0;
		for (Node node : nodeGenes) {
			if (node.type == Node.SIMPLE_SEQUENCE) {
				sum += Math.log(node.parameter);
				count++;
			}
		}
		double avg = sum / count;
		double sd = 0;
		for (Node node : nodeGenes) {
			if (node.type == Node.SIMPLE_SEQUENCE) {
				sd += (Math.log(node.parameter) - avg)
						* (Math.log(node.parameter) - avg);
			}
		}
		sd = Math.sqrt(sd / count);
		return sd;
	}

	public String printSimpleModel() {
		StringBuilder builder = new StringBuilder();
		for (Connection connection : connectionGenes) {
			if (connection.enabled) {
				builder.append("(" + connection.from.name + connection.to.name
						+ ")");
			}
		}
		return builder.toString();
	}

	public void fixSignalSequences() {
		if (Constants.optimizeSeqString) {
			for (int i = 1; i < nodeGenes.size(); i++) {
				for (int j = 0; j < i; j++) {
					Node nodeI = nodeGenes.get(i);
					Node nodeJ = nodeGenes.get(j);
					if (nodeI.type == Node.SIMPLE_SEQUENCE
							&& nodeJ.type == Node.SIMPLE_SEQUENCE) {
						if (nodeI.string.equals(nodeJ.string)) {
							int randomUnused = getRandomUnusedStr();
							if (randomUnused != -1) {
								nodeI.parameter = Static.seqK[randomUnused];
								nodeI.string = Static.seqStr[randomUnused];
							}
							break;
						}
					}
				}
			}
		}
	}

}
